#pragma once


#include <iostream>
#include <string>
#include <queue>
#include <pthread.h>

const static int default_cap = 5;

template<typename T>
class BlockQueue
{
private:
    bool Isfull()
    {
        return _block_queue.size() == this->_max_cap;
    }
    bool IsEmpty()
    {
        return _block_queue.empty();
    }
public:
    BlockQueue(int cap = default_cap)
        : _max_cap(cap)
    {
        pthread_mutex_init(&_mutex, nullptr);
        pthread_cond_init(&_cond_p, nullptr);
        pthread_cond_init(&_cond_c, nullptr);
    }

    void Pop(T* out)
    {
        pthread_mutex_lock(&_mutex);
        while (IsEmpty())
        {
            // 如果这里出现两个消费者在条件变量的阻塞队列里
            // 有朝一日生产者用 pthread_cond_broadcast 去唤醒这两个消费者时
            // 必有一个是等待 _mutex 的
            // 如果另一个消费者将唯一的数据取走后，这个消费者却依然能够拿数据，发生段错误
            // 这种条件尚未满足，但线程被异常唤醒的情况，叫做伪唤醒
            // 未了避免这种情况，需要使用 while 而不是 if 
            pthread_cond_wait(&_cond_c, &_mutex);
        }
        // 没有空 或者 被唤醒 
        *out = _block_queue.front();
        _block_queue.pop();
        pthread_mutex_unlock(&_mutex);
        // 放在解锁之前也可以，只是可能会导致消费者因为得不到锁而被继续阻塞 
        pthread_cond_signal(&_cond_p);
    }

    void Equeue(const T& in)
    {
        pthread_mutex_lock(&_mutex);
        while (Isfull())
        {
            // 满了，生产者不能再生产，需要等待 
            // 在临界区里等待 
            // 所以 pthread_cond_wait 函数除了排队等待，还会原子性的释放传入的锁 
            pthread_cond_wait(&_cond_p, &_mutex);
            // 此时醒来在临界区？对，不过在被返回之前，会重新申请锁 
        }
        // 阻塞队列没有满 或者 被唤醒了 
        _block_queue.push(in); // 将数据放进阻塞队列，此时一定有数据 
        pthread_mutex_unlock(&_mutex);
        pthread_cond_signal(&_cond_c);

        // 让消费者消费 
    }

    ~BlockQueue()
    {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_cond_p);
        pthread_cond_destroy(&_cond_c);
    }

private:
    std::queue<T> _block_queue;  // 维护成临界资源 
    int _max_cap;                // 最大容量 
    pthread_mutex_t _mutex;      // 锁 
    pthread_cond_t _cond_p;      // 生产者条件变量 
    pthread_cond_t _cond_c;      // 消费者条件变量 
};

